﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Threading;
using NsbExtensions.Messages.Management.Monitoring;
using NsbExtensions.Utilities;

namespace NsbExtensions.Server.Management.EndpointMonitor
{
    public delegate void ArchiveUpdateDelegate(EndpointStatusUpdateNotification update);

    public class EndpointStatusAggregationService : TimerServiceBase, IEndpointStatusAggregationService
    {
        private readonly ReaderWriterLock _aggLock = new ReaderWriterLock();

        private readonly Dictionary<string, Dictionary<string, EndpointMeasurementSummary>> _aggregations =
            new Dictionary<string, Dictionary<string, EndpointMeasurementSummary>>();

        private DateTime _aggregationStarted;
        public TimeSpan ReadWriteTimeout { get; set; }
        public IEndpointStatusRepository Repository { get; set; }

        #region IEndpointStatusAggregationService Members

        public IEndpointMonitorServerService EndpointMonitor { get; set; }

        public TimeSpan StatusBroadcastInterval
        {
            get { return Interval; }
            set { Interval = value; }
        }

        public IMessagingEndpoint Endpoint { get; set; }

        #endregion

        protected override void StartService()
        {
            PreventConcurrentIntervalProcessing = true;
            if (ReadWriteTimeout == TimeSpan.Zero) ReadWriteTimeout = TimeSpan.FromSeconds(30);

            if (EndpointMonitor == null)
                throw new ConfigurationErrorsException(
                    "No endpoint monitor has been configured in the system. The EndpointStatusAggregationService requires a valid EndpointMonitor be in use at this endpoint.");

            EndpointMonitor.EndpointStatusExpired += EndpointMonitor_EndpointStatusExpired;
            EndpointMonitor.EndpointStatusUpdated += EndpointMonitor_EndpointStatusUpdated;

            base.StartService();
        }

        protected override void ShutDownService()
        {
            base.ShutDownService();
            EndpointMonitor.EndpointStatusExpired -= EndpointMonitor_EndpointStatusExpired;
            EndpointMonitor.EndpointStatusUpdated -= EndpointMonitor_EndpointStatusUpdated;
        }


        protected void ClearAggregationsForEndpoint(string endpointId)
        {
            using (new WriterLock(_aggLock, ReadWriteTimeout))
            {
                if (_aggregations.ContainsKey(endpointId))
                    _aggregations.Remove(endpointId);
            }
        }

        protected virtual void AggregateEndpointMeasurements(EndpointStatusUpdateNotification update)
        {
            if (update == null || update.EndpointMeasurements == null || update.EndpointMeasurements.Count < 1)
                return;

            if (_aggregationStarted == DateTime.MinValue)
                _aggregationStarted = DateTime.Now.ToUniversalTime();

            using (new WriterLock(_aggLock, ReadWriteTimeout))
            {
                Dictionary<string, EndpointMeasurementSummary> measurements;

                if (_aggregations.ContainsKey(update.EndpointID))
                    measurements = _aggregations[update.EndpointID];
                else
                {
                    measurements = new Dictionary<string, EndpointMeasurementSummary>();
                    _aggregations[update.EndpointID] = measurements;
                }

                foreach (EndpointMeasurement measurement in update.EndpointMeasurements)
                {
                    EndpointMeasurementSummary summary;
                    if (measurements.ContainsKey(measurement.Name))
                    {
                        summary = measurements[measurement.Name];
                    }
                    else
                    {
                        summary = new EndpointMeasurementSummary();
                        measurements.Add(measurement.Name, summary);
                    }

                    Aggregate(measurement, summary);
                }
            }

            ArchiveStatusUpdate(update, true);
        }

        protected virtual void ArchiveStatusUpdate(EndpointStatusUpdateNotification update, bool async)
        {
            if (Repository == null)
                return;

            ArchiveUpdateDelegate del = Repository.AddStatus;
            if (async)
                del.BeginInvoke(update, null, null);
            else
            {
                del.Invoke(update);
            }
        }


        protected override void PerformTimerAction()
        {
            using (new BusyHandle(this))
            {
                BroadcastStatusUpdate();
            }
        }

        protected virtual void BroadcastStatusUpdate()
        {
            var endpoints = new List<EndpointStatusSummary>();

            using (new ReaderLock(_aggLock, ReadWriteTimeout))
            {
                foreach (string endpointId in _aggregations.Keys)
                {
                    Dictionary<string, EndpointMeasurementSummary> measurements = _aggregations[endpointId];
                    var measurementList = new List<EndpointMeasurementSummary>(measurements.Values);

                    var endpoint = new EndpointStatusSummary
                                       {
                                           EndpointID = endpointId,
                                           StatusMeasurements = measurementList
                                       };
                    endpoints.Add(endpoint);
                }

                var notification = new EndpointStatusSummaryNotification
                                       {
                                           AggregationStartTime = _aggregationStarted,
                                           AggregationStopTime = DateTime.Now,
                                           ActiveEndpoints = endpoints
                                       };

                Endpoint.Bus.Publish(notification);
            }
        }

        private void EndpointMonitor_EndpointStatusUpdated(object sender, EndpointStatusEventArgs e)
        {
            if (e.LastStatusMessage is EndpointStatusUpdateNotification)
            {
                using (new BusyHandle(this))
                {
                    AggregateEndpointMeasurements((EndpointStatusUpdateNotification) e.LastStatusMessage);
                }
            }
            else if (e.LastStatusMessage is EndpointOfflineNotification)
            {
                ClearAggregationsForEndpoint(e.EndpointID);
            }
        }

        private void EndpointMonitor_EndpointStatusExpired(object sender, EndpointStatusEventArgs e)
        {
            ClearAggregationsForEndpoint(e.EndpointID);
        }

        private static void Aggregate(EndpointMeasurement measurement, EndpointMeasurementSummary summary)
        {
            summary.LastMeasurement = measurement;
            summary.SampleCount++;


            if (measurement.DataType == MeasurementDataType.Numeric)
            {
                double doubleVal;
                Double.TryParse(measurement.Value, out doubleVal);

                summary.Sum += doubleVal;
                summary.Average = summary.Sum/summary.SampleCount;

                if
                    (doubleVal > summary.Max) summary.Max = doubleVal;
                else if
                    (doubleVal < summary.Min) summary.Min = doubleVal;
            }
        }
    }
}